<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>五子棋游戏</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #cvs {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);

            box-shadow: 5px 5px 15px 10px #333;
        }

        #title {
            text-align: center;
        }
    </style>
</head>
<body>
<h1 id="title"></h1>

<canvas id="cvs" width="450" height="450"></canvas>
<script type="text/javascript">
    var title = document.getElementById('title');
    //获取到画板
    var cvs = document.getElementById('cvs');
    //获取上下文
    var ctx = cvs.getContext("2d");

    var option = {
        0: 20,
        1: 40,
        2: 80,
        3: 200,
        4: 1000
    };

    init();//游戏的初始化

    var G = {
        black: true
    };

    //1、画棋盘 15行 15列，每行30像素，每列30像素
    function drawPlaint() {
        //循环画出行和列的直线
        ctx.strokeStyle = '#666';
        ctx.textBaseline = "middle";//垂直对齐
        ctx.textAlign = "center";//水平对齐
        for (var i = 0; i < 15; i++) {
            var x1 = 15, x2 = 435, y = 15 + i * 30;

            //画出行
            ctx.moveTo(x1, y);
            ctx.lineTo(x2, y);
            ctx.stroke();
            //画出列
            ctx.moveTo(y, x1);
            ctx.lineTo(y, x2);
            ctx.stroke();

            ctx.beginPath();
            ctx.fillText(i, 3, y);//写行号
            ctx.closePath();

            ctx.beginPath();
            ctx.fillText(i, y, 8);//写列号
            ctx.closePath();
        }
    }
    drawPlaint();
    var lastPointBlack = null;
    //2、画棋子，棋子在行列线的交叉处
    function drawZi(x, y, black) {

        if (lastPointBlack != null) {//把最后一粒子还原
            drawZi2(lastPointBlack.x, lastPointBlack.y, lastPointBlack.state == 1)
        }
        //把当前子画一个样式
        lastPointBlack = getQiZi(x, y);
        x = x * 30 + 15;
        y = y * 30 + 15;
        ctx.beginPath();
        ctx.arc(x, y, 13, 0, 2 * Math.PI);
        ctx.closePath();
        if (black) {
            ctx.fillStyle = "purple";
        } else {
            ctx.fillStyle = "#eee";
            ctx.lineWidth = 2;
            ctx.strokeStyle = "#333";
            ctx.stroke();
        }
        ctx.fill();


    }
    //drawZi(false);
    function drawZi2(x, y, black) {
        //算出在x行y列的坐标像素点
        x = x * 30 + 15;
        y = y * 30 + 15;
        ctx.beginPath();
        ctx.arc(x, y, 13, 0, 2 * Math.PI);
        ctx.closePath();
        if (black) {
            ctx.fillStyle = "#333";
        } else {
            ctx.fillStyle = "#eee";
            ctx.lineWidth = 2;
            ctx.strokeStyle = "#333";
            ctx.stroke();
        }
        ctx.fill();
    }
    function init() {//游戏的初始化
        var points = [];//二维数组，存所有的点
        var fangAns = [];
        var singlePoints = [];//一维数组，为了点的排序方便


        function Point(x, y) {
            this.fangAns = []; //方案和所有的点是多对多的关系
            this.score = 0;
            this.x = x;
            this.y = y;
            this.state = 0;//0表示没子，1表示黑子，2表示白子
        }

        function FangAn() {
            this.points = [];//方案和所有的点是多对多的关系
            this.step = 0;
            this.state = 0;//0表示没子，1表示全是黑子，2表示全是白子，3表示黑子和白子都有
            this.add = function (x, y) {
                var point = points[x][y];
                this.points.push(point);
                point.fangAns.push(this);//方案添加棋子的同时，棋子也添加方案
            }
        }

        //将所有的点存到二维数组中
        var point = null;
        for (var i = 0; i < 15; i++) {
            points[i] = [];
            for (var j = 0; j < 15; j++) {
                point = new Point(i, j);
                points[i][j] = point;
                singlePoints.push(point);

            }
        }

        //添加 列 方案
        var temp = null;
        for (var x = 0; x < 15; x++) {
            for (var y = 0; y < 11; y++) {
                temp = new FangAn();
                for (var k = 0; k < 5; k++) {
                    temp.add(x, y + k);
                }
                fangAns.push(temp);
            }
        }
        //添加 行 方案
        for (var x = 0; x < 15; x++) {
            for (var y = 0; y < 11; y++) {
                temp = new FangAn();
                for (var k = 0; k < 5; k++) {
                    temp.add(y + k, x);
                }
                fangAns.push(temp);
            }
        }
        //添加 斜 方案
        for (var x = 0; x < 11; x++) {
            for (var y = 0; y < 11; y++) {
                temp = new FangAn();
                for (var k = 0; k < 5; k++) {
                    temp.add(x + k, y + k);
                }
                fangAns.push(temp);
            }
        }
        //添加 反斜 方案
        for (var x = 14; x > 3; x--) {
            for (var y = 0; y < 11; y++) {
                temp = new FangAn();
                for (var k = 0; k < 5; k++) {
                    temp.add(x - k, y + k);
                }
                fangAns.push(temp);
            }
        }
        window.points = points;//变成了全局变量
        window.fangAns = fangAns;
        window.singlePoints = singlePoints;
    }
    //3、用户点击棋盘，画出相应的棋子，棋子要求黑白间隔
    cvs.onclick = function (e) {
        var x = e.offsetX;
        var y = e.offsetY;
        x = Math.floor(x / 30);//算出棋子在第几列
        y = Math.floor(y / 30);//算出棋子在第几行
        if (!hasZi(x, y)) {//没有子才画子
            handlerLuoZi(x, y, false);//false为白子
        }
    }
    function getQiZi(x, y) {
        if (x < 0 || x > 14 || y < 0 || y > 14) {
            console.error("这个(%d,%d)点不存在", x, y);
            return;
        }
        return points[x][y];
    }
    function hasZi(x, y) {
        return points[x][y].state != 0;
    }

    //4、判断输赢  满足一行、一列、斜线或反斜线都表示赢了，赢了之后要显示赢家的颜色，棋盘不能再下棋
    function handlerLuoZi(x, y, black) {
        //将这个子的己方方案的step累加
        var point = getQiZi(x, y);
        point.state = black ? 1 : 2;//黑子为1，白子为2
        drawZi(x, y, black);//棋子在行列交叉处

        //有一个隐患：计算机下完棋，G.black不会改变颜色
        var arr = [2, 1]
        var over = false;
        if (point) {
            for (var i in point.fangAns) {
                var item = point.fangAns[i];//item表示这个点的某一个方案
                if (item.state == 3) {//0表示没子，1表示全是黑子，2表示全是白子，3表示黑子和白子都有
                    continue;
                }
                if (item.state == 0) {//没有子就把这个改为己方的
                    item.state = arr[Boolean(black) * 1];
                }
                if (item.state == arr[Boolean(black) * 1]) {//说明是己方方案
                    item.step++;
                    //title.textContent="轮到"+(black?"黑方":"白方")+"落子了";
                } else if (item.state != 0) {//说明是对方的,该方案作废
                    //title.textContent="轮到"+(black?"白方":"黑方")+"落子了";
                    item.state = 3;
                }
                if (item.step == 5) {
                    for (var j = 0; j < 5; j++) {
                        var x1 = item.points[j].x*30+15;
                        var y1 = item.points[j].y*30+15;
                        ctx.beginPath();
                        ctx.arc(x1, y1, 13, 0, 2 * Math.PI);
                        ctx.closePath();
                        if (black) {
                            ctx.fillStyle = "purple";
                        } else {
                            ctx.fillStyle = "#eee";
                            ctx.lineWidth = 2;
                            ctx.strokeStyle = "#333";
                            ctx.stroke();
                        }
                        ctx.fill();
                    }
                    over = true;
                    //alert("棋局结束！"+(black?"黑方":"白方")+"赢了");
                    title.textContent = "棋局结束！" + (black ? "黑方" : "白方") + "赢了";
                    cvs.onclick = function () {
                        //alert("棋局结束！"+(black?"黑方":"白方")+"赢了");
                        title.textContent = "棋局结束！" + (black ? "黑方" : "白方") + "赢了";


                    }
                    break;
                }
            }
        }
        // 如果这个方案是对方的，那么这个方案作废
        //如果这个方案有效，并且已经连成了5个，即step==5时棋局结束，black赢了

        if (!black && !over) {//如果白子下棋并且棋局没有结束，那么计算机即黑子下棋
            computerPlay();
            //alert("计算机下棋");
        }
    }


    //5、实现一个算法陪伴玩家下棋
    function computerPlay(x, y) {
        //已经下过的点去掉，不参与评分
        singlePoints = singlePoints.filter(function (item) {
            return item.state == 0;
        })

        for (i in singlePoints) {
            var point = singlePoints[i];
            point.score = 0;//在算分之前score重置为0
            point.score = point.fangAns.map(function (fangan) {//评分
                if (fangan.state == 3) {//作废的方案不给分
                    return 0;
                }
                return option[fangan.step];
            }).reduce(function (pre, cur) {
                return pre + cur;
            });

        }
        //将评分进行排序
        singlePoints.sort(function (a, b) {
            return b.score - a.score;
        });
        var greatPoint = singlePoints[0];
        handlerLuoZi(greatPoint.x, greatPoint.y, true);
    }

</script>
</body>
</html>